#include "defines.h"
#include "alarm.h"
#include "kozos.h"
#include "intr.h"

#define TIMER_INTR_PER_SEC  5

#define TCR8_2   ((volatile uint8 *) 0xffff90)
#define TCR8_3   ((volatile uint8 *) 0xffff91)
#define TCORA_2  ((volatile uint8 *) 0xffff94)
#define TCORA_3  ((volatile uint8 *) 0xffff95)
#define TCNT8_2  ((volatile uint8 *) 0xffff98)
#define TCNT8_3  ((volatile uint8 *) 0xffff99)
#define TCSR8_2  ((volatile uint8 *) 0xffff92)
#define TCSR8_3  ((volatile uint8 *) 0xffff93)

static uint32 total_sec = 0;

uint32 system_time()
{
    return total_sec;
}

static void total_sec_add()
{
    static int s = 0;
    if (++s == TIMER_INTR_PER_SEC) {
        total_sec++;
        s = 0;
    }
}

typedef struct _alarm_task {
    struct _alarm_task *next;
    uint32 count;
    kz_msgbox_id_t id;
    int size;
    char *msg;
} alarm_task;

static alarm_task *alarm_head;
static kz_msgbox_id_t alarm_msgbox;

// 優先度が低いスレッドからのみ呼べる。
int alarm_s(uint16 sec, kz_msgbox_id_t id, int size, char *msg)
{
    if (sec > 0) {
        alarm_task *p = kz_kmalloc(sizeof(alarm_task));
        p->next = NULL; // 一応
        p->count = (uint32)sec * TIMER_INTR_PER_SEC;
        p->id = id;
        p->size = size;
        p->msg = msg;
        kz_send(alarm_msgbox, 0, (char *)p);
    } else {
        kz_send(id, size, msg); // キューに入れず直ちに返す
    }

    return 0;
}

// 実際の時間は割込間隔の倍数に切り上げ。
int alarm_ms(uint16 msec, kz_msgbox_id_t id, int size, char *msg)
{
    if (msec > 0) {
        alarm_task *p = kz_kmalloc(sizeof(alarm_task));
        p->next = NULL;
        p->count = (msec - 1) / (1000 / TIMER_INTR_PER_SEC) + 1;
        p->id = id;
        p->size = size;
        p->msg = msg;
        kz_send(alarm_msgbox, 0, (char *)p);
    } else {
        kz_send(id, size, msg);
    }

    return 0;
}

static void alarm_send() // 割込から呼ぶ
{
    if (alarm_head && --alarm_head->count == 0) {
        alarm_task *p, *q;
        for (p = alarm_head; p && p->count == 0; ) {
            kx_send(p->id, p->size, p->msg);
            q = p;
            p = p->next;
            kx_kmfree(q);
        }
        alarm_head = p;
    }
}

static void alarm_intr()
{
    *TCSR8_3 &= ~0x40; /* 割込リセット */
    alarm_send();
    total_sec_add();
}

static void alarm_init()
{
    alarm_head = NULL;
    alarm_msgbox = kz_getmsgbox();

    /* 1/5秒ごとに割り込み */
    kz_setintr(SOFTVEC_TYPE_T8INTR_23, alarm_intr);
    *TCR8_2 = 0x00;
    *TCR8_3 = 0x4c;
    *TCORA_2 = 0xf9;
    *TCORA_3 = 0xf9;
    *TCNT8_2 = 0x00;
    *TCNT8_3 = 0x00;
    *TCSR8_3 = 0x00;
    *TCR8_2 = 0x0a;
}

static void alarm_add(alarm_task *p) {
    uint32 s;
    alarm_task *q0, *q;

    INTR_DISABLE;

    s = 0;
    q0 = NULL;
    q = alarm_head;

    while (q && s + q->count <= p->count) {
        s += q->count;
        q0 = q;
        q = q->next;
    }

    if (q0) {
        q0->next = p;
    } else {
        alarm_head = p;
    }

    p->next = q;
    p->count -= s;

    if (q) {
        q->count -= p->count;
    }

    INTR_ENABLE;
}

int alarm_main(int argc, char *argv[])
{
    char *p;

    alarm_init();

    while (1) {
        kz_recv(alarm_msgbox, NULL, &p, 0);
        alarm_add((alarm_task *)p);
    }

    /* 来ない */

    return 0;
}
